﻿#region Header

/*
Remote views based on linq
By

Pascal Hauser 
Dipl. Ing. in Informatik, Hochschule für Technik Rapperswil, 2006
Master Thesis, Hochschule für Technik Rapperswil, 2008-2010

and

Raphael Gfeller
Dipl. Ing. in Informatik, Hochschule für Technik Rapperswil, 2006
Master Thesis, Hochschule für Technik Rapperswil, 2008-2010

*/

#endregion

#region Usings

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading;
using System.Windows.Threading;
using Sebarf.Diagnostics.Interfaces;
using Utils;

#endregion

namespace InterLinq.Test.PD {
	/// <summary>
	/// Description of the class
	/// </summary>
	public class AsnychPagedCollection<T> {
		#region Public Properties

		public const short CacheLoadFactor = 10;
		public const short CacheReloadFactor = 5;
		public int ViewSize { get; private set; }
		public int MaxElements { get; private set; }

		public ObservableCollection<T> Elements {
			get { return m_viewedElements; }
		}

		#endregion

		#region Public Methods

		public AsnychPagedCollection(int viewSize, Dispatcher dispatcher, _GetElementCount elementCount,
									  _GetElements retriveElements) {
			ViewSize = viewSize;
			for (int i = 0; i < viewSize; i++) {
				m_viewedElements.Add(default(T));
			}
			m_query = retriveElements;
			MaxElements = elementCount();
			m_dispatcher = dispatcher;
			m_timer.Tick += OnTimerTick;
			m_timer.Interval = new TimeSpan(0, 0, 0, 0, 100);
			View(0);
		}

		public void View(int offset) {
			lock (this) {
				m_requests.Add(offset);
			}
			m_viewOffset = offset;
			m_currentAction++;
			ViewInternal(offset);
		}

		public void CheckDisplayedView() {
			if (IsReloadNeeded()) {
				if (m_dispatcher == null) {
					LoadElements();
				}
				else {
					m_dispatcher.BeginInvoke(DispatcherPriority.Normal, new NullDelegate(LoadElements));
					//m_timer.Start();
				}
			}
		}

		private bool IsReloadNeeded() {
			for (int i = -1 * CacheReloadFactor * ViewSize; i < CacheReloadFactor * ViewSize; i++) {
				int id = i + m_viewOffset;
				if (id >= 0 && id < MaxElements) {
					if (!m_cache.ContainsKey(id)) {
						return true;
					}
				}
			}
			return false;
		}

		public void LoadElements() {
			lock (this) {
				if (m_working) {
					return;
				}
				m_working = true;
			}

			int fromId2 = Math.Max(0, m_viewOffset - CacheLoadFactor * ViewSize);
			int to2 = Math.Min(MaxElements, m_viewOffset + CacheLoadFactor * ViewSize);
			ThreadPool.QueueUserWorkItem(delegate {
				//m_workerthread = new Thread(new ThreadStart(delegate() {
				bool hasToReload = false;
				try {
					Logger.WriteDebug("Start loading elements");
					var measurements = new PerformanceTimer();
					measurements.Start();
					while (m_requests.Count > 0) {
						lock (this) {
							m_viewOffset = m_requests[m_requests.Count - 1];
							m_requests.Clear();
						}
						if (IsReloadNeeded()) {
							int fromId = Math.Max(0, m_viewOffset - CacheLoadFactor * ViewSize);
							int to = Math.Min(MaxElements, m_viewOffset + CacheLoadFactor * ViewSize);

							var measurements5 = new PerformanceTimer();
							measurements5.Start();
							Dictionary<int, object> results = m_query(fromId, to);
							measurements5.Stop();
							Logger.WriteDebug("	var results = m_query(fromId, to);" +
											   measurements5.ElapsedTimeMilliseconds.Miliseconds);
							foreach (var entry in results) {
								if (!m_cache.ContainsKey(entry.Key)) {
									m_cache.Add(entry.Key, (T)entry.Value);
								}
							}
							var measurements2 = new PerformanceTimer();
							measurements2.Start();
							KeyValuePair<int, T>[] entriesToRemove =
								(from p in m_cache where p.Key < fromId || p.Key > to select p).ToArray();
							measurements2.Stop();
							Logger.WriteDebug(
								" entriesToRemove = (from p in m_cache where p.Key < fromId || p.Key > to select p).ToArray()" +
								measurements2.ElapsedTimeMilliseconds.Miliseconds);
							foreach (var entry in entriesToRemove) {
								m_cache.Remove(entry.Key);
							}
							if (m_dispatcher == null) {
								var measurements3 = new PerformanceTimer();
								measurements3.Start();
								ViewInternal(m_viewOffset);
								measurements3.Stop();
								Logger.WriteDebug("ViewInternal(m_viewOffset)" +
												   measurements3.ElapsedTimeMilliseconds.Miliseconds);
							}
							else {
								var measurements4 = new PerformanceTimer();
								measurements4.Start();
								m_dispatcher.BeginInvoke(DispatcherPriority.Normal,
														  new NullDelegate(
															delegate { ViewInternal(m_viewOffset); }));
								measurements4.Stop();
								Logger.WriteDebug("ViewInternal(m_viewOffset)" +
												   measurements4.ElapsedTimeMilliseconds.Miliseconds);
							}
						}
					}
					measurements.Stop();
					Logger.WriteDebug("PerformanceTimer" +
									   measurements.ElapsedTimeMilliseconds.Miliseconds);
					Logger.WriteDebug("End loading elements");
				}
				catch (Exception ex) {
					Check.AssertException(false, ex.Message);
				}
				finally {
					lock (this) {
						m_working = false;
						hasToReload = m_requests.Count > 0;
					}
				}
				if (hasToReload) {
					LoadElements();
				}
			});
			//m_workerthread.IsBackground = true;
			//m_workerthread.Start();
		}

		#endregion

		#region Private Methods

		private void ViewInternal(int offset) {
			m_viewOffset = offset;
			for (int i = 0; i < ViewSize; i++) {
				int id = i + m_viewOffset;
				m_viewedElements[i] = m_cache.ContainsKey(id) ? m_cache[id] : default(T);
			}
			CheckDisplayedView();
		}

		private void OnTimerTick(object sender, EventArgs e) {
			m_timer.Stop();
			LoadElements();
		}

		#endregion

		#region Private Fields

		private readonly Dispatcher m_dispatcher;
		private readonly _GetElements m_query;
		private readonly List<int> m_requests = new List<int>();
		private readonly DispatcherTimer m_timer = new DispatcherTimer();
		private readonly ObservableCollection<T> m_viewedElements = new ObservableCollection<T>();
		public Dictionary<int, T> m_cache = new Dictionary<int, T>();
		private int m_currentAction;
		private int m_viewOffset;
		private Thread m_workerthread;
		private bool m_working;

		#endregion

		#region Public Types

		public delegate int _GetElementCount();

		public delegate Dictionary<int, object> _GetElements(int from, int to);

		#endregion
	}
}